package com.xneure.database;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

import com.xneure.database.annotation.DynamicResults;
import com.xneure.database.dao.DataBaseTable;
import com.xneure.database.dao.ResultFilter;
import com.xneure.database.sql.QueryMappingParser;
import com.xneure.utils.ObjectUtil;

@Intercepts(@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = { Statement.class }))
public class QuerResultInterceptor implements Interceptor {
	private QueryMappingParser qmp = new QueryMappingParser();
	private ResultFilter defFilter = new ResultFilter();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Method currMethod = DynamicDataSource.getCurrMethod();
		if (null != currMethod) {
			DynamicResults anno = currMethod.getAnnotation(DynamicResults.class);
			if (null != anno) {
				ResultFilter filter = defFilter;
				Statement stmt = (Statement) invocation.getArgs()[0];
				ResultSetHandler target = (ResultSetHandler) invocation.getTarget();
				ParameterHandler parameterHandler = ObjectUtil.getFieldValue(target, "parameterHandler");
				Object parameterObj = parameterHandler.getParameterObject();
				if (anno.value().equals(Object.class)) {
					if (parameterObj instanceof Map) {
						Map<?, ?> parMap = (Map<?, ?>) parameterObj;
						for (Entry<?, ?> key : parMap.entrySet()) {
							if (ObjectUtil.isNull(key.getValue())) {
								continue;
							}
							if (key.getValue() instanceof Class) {
								parameterObj = key.getValue();
								break;
							} else if (key.getValue() instanceof DataBaseTable) {
								parameterObj = key.getValue().getClass();
								break;
							} else if (key.getValue().getClass().isArray()) {
								Object o = ((Object[]) key.getValue())[0];
								if (o instanceof Class) {
									parameterObj = o;
								} else if (o instanceof DataBaseTable) {
									parameterObj = o.getClass();
								}
								break;
							} else if (key.getValue() instanceof ResultFilter) {
								filter = (ResultFilter) key.getValue();
							}
						}
					} else {
						if (parameterObj instanceof ResultFilter) {
							filter = (ResultFilter) parameterObj;
						}
						parameterObj = null;
					}
				} else {
					parameterObj = anno.value();
				}
				if (null == parameterObj || parameterObj instanceof Map) {
					parameterObj = currMethod.getGenericReturnType();
					if (parameterObj instanceof ParameterizedType) {
						parameterObj = ((ParameterizedType) parameterObj).getActualTypeArguments()[0];
						if (parameterObj instanceof ParameterizedType) {
							parameterObj = ((ParameterizedType) parameterObj).getRawType();
						}
					}
				}
				if (parameterObj instanceof Class == false) {
					parameterObj = parameterObj.getClass();
				}
				if (ObjectUtil.isBasicType(parameterObj) == false) {
					return extractData(stmt.getResultSet(), (Class<?>) parameterObj, filter);
				}
			}
		}
		return invocation.proceed();
	}

	/*
	 * 组装查询结果
	 */
	public Object extractData(ResultSet rs, Class<?> cls, ResultFilter filter) throws SQLException {
		try {
			ResultSetMetaData md = rs.getMetaData();
			int num = md.getColumnCount();
			List<Object> listOfRows = new ArrayList<Object>();
			Map<String, Object> mapOfColValues;
			String colName;
			Object val;
			if (isBaseType(cls)) {
				if (rs.next()) {
					return ObjectUtil.convertValue(rs.getObject(1), cls);
				} else {
					return null;
				}
			} else if (InputStream.class.isAssignableFrom(cls)) {
				if (rs.next()) {
					return rs.getBinaryStream(1);
				} else {
					return null;
				}
			} else if (Blob.class.isAssignableFrom(cls)) {
				if (rs.next()) {
					return rs.getBlob(1);
				} else {
					return null;
				}
			} else if (Clob.class.isAssignableFrom(cls)) {
				if (rs.next()) {
					return rs.getClob(1);
				} else {
					return null;
				}
			} else {
				while (rs.next()) {
					mapOfColValues = new HashMap<String, Object>(num);
					Object transform;
					if (Map.class.isAssignableFrom(cls) || List.class.isAssignableFrom(cls)) {
						for (int i = 1; i <= num; i++) {
							colName = filter.getColumnName(md.getColumnLabel(i));
							val = filter.getValue(colName, rs.getObject(i));
							if (filter.skip(colName, val) == false) {
								mapOfColValues.put(colName, val);
							}
						}
						transform = filter.transform(mapOfColValues);
					} else {
						for (int i = 1; i <= num; i++) {
							colName = filter.getColumnName(md.getColumnLabel(i).toLowerCase());
							val = filter.getValue(colName, rs.getObject(i));
							if (filter.skip(colName, val) == false) {
								mapOfColValues.put(colName, val);
							}
						}
						transform = filter.transform(qmp.toObject(cls, mapOfColValues));
					}
					if (null != transform) {
						listOfRows.add(transform);
					}
				}
			}
			return listOfRows;
		} finally {
			rs.close();
		}
	}

	public boolean isBaseType(Class<?> cls) {
		cls = ObjectUtil.getObjectClass(cls);
		String lowerCase = cls.getSimpleName().toLowerCase();
		if (lowerCase.equals("date")) {
			return true;
		} else if (lowerCase.equals("bigdecimal")) {
			return true;
		} else if (lowerCase.equals("guid")) {
			return true;
		} else if (lowerCase.equals("uuid")) {
			return true;
		} else if (lowerCase.equals("string")) {
			return true;
		} else if (lowerCase.equals("long")) {
			return true;
		} else if (lowerCase.equals("integer") || lowerCase.equals("int")) {
			return true;
		} else if (lowerCase.equals("double")) {
			return true;
		} else if (lowerCase.equals("float")) {
			return true;
		} else if (lowerCase.equals("boolean")) {
			return true;
		} else if (lowerCase.equals("byte")) {
			return true;
		} else if (lowerCase.equals("character") || lowerCase.equals("char")) {
			return true;
		} else if (lowerCase.equals("timestamp")) {
			return true;
		} else if (cls.isArray()) {
			return true;
		} else if (cls.isEnum()) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof ResultSetHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties arg0) {
	}
}
